package cn.z2huo.productcenter.management.app.service.impl.risk;

import cn.z2huo.core.exception.ServerException;
import cn.z2huo.productcenter.enums.flag.*;
import cn.z2huo.productcenter.management.app.dao.api.risk.RiskDAO;
import cn.z2huo.productcenter.management.app.service.api.risk.RiskUnavailableService;
import cn.z2huo.productcenter.management.app.service.api.risk.release.RiskSyncService;
import cn.z2huo.productcenter.management.model.vo.risk.AvailableVO;
import cn.z2huo.productcenter.mapper.risk.RiskMapper;
import cn.z2huo.productcenter.mapper.risk.release.RiskReleaseMapper;
import cn.z2huo.productcenter.model.dataobject.risk.RiskDO;
import cn.z2huo.productcenter.model.dataobject.risk.release.RiskReleaseDO;
import cn.z2huo.productcenter.util.RedisKeyGenerateUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RKeys;
import org.redisson.api.RedissonClient;
import org.redisson.api.options.KeysOptions;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

import java.time.Instant;
import java.util.concurrent.ScheduledFuture;

/**
 * <p>
 *
 * @author z2huo
 */
@Slf4j
@Service
@Primary
@RequiredArgsConstructor
public class RiskUnavailableDoubleDeleteServiceImpl implements RiskUnavailableService {

    private final RiskMapper riskMapper;

    private final TransactionTemplate transactionTemplate;

    private final RiskReleaseMapper riskReleaseMapper;

    private final RiskDAO riskDAO;

    private final RiskSyncService riskSyncService;

    private final RedissonClient redissonClient;

    private final TaskScheduler taskScheduler;

    /**
     * <p>注销产品
     *
     * <p>注销时是否需要看审核状态？注销，注销的是已发布的产品，而不是正在配置的产品，感觉这个配置和发布两者应该分开
     * <p>假如配置需要审核流程，启用和注销也需要单独的审核流程，那么可能一个产品会同时存在多个审核流程，即配置审核流程在进行配置审核
     * 而注销审核流程在进行已发布产品的注销功能。
     * 应该是要将配置页面和发布页面分开的，即一个页面可以产品当前未发布正在配置并走配置审核流程的产品。
     * 另一个页面是发布完成的产品，这个页面可以进行注销。
     *
     * @param availableVO 待禁用产品
     */
    @Override
    public void unavailable(AvailableVO availableVO) {

        String riskCode = availableVO.getRiskCode();
        Long riskId = availableVO.getId();

        if (StringUtils.isBlank(riskCode) || riskId == null) {
            throw new ServerException("产品代码和 ID 未提供");
        }

        // 校验产品是否可以启用
        LambdaQueryWrapper<RiskDO> queryWrapper = Wrappers.lambdaQuery(RiskDO.class);
        queryWrapper.select(RiskDO::getAvailableFlag, RiskDO::getAuditFlag, RiskDO::getDeleteFlag, RiskDO::getPublishFlag)
                .eq(RiskDO::getId, riskId)
                .eq(RiskDO::getRiskCode, riskCode)
                .last("limit 1");
        RiskDO flagRiskDO = riskMapper.selectOne(queryWrapper);
        if (flagRiskDO == null || DeleteFlagEnum.DELETE.getValue().equals(flagRiskDO.getDeleteFlag())) {
            throw new ServerException("待启用产品不存在");
        }
        AvailableFlagEnum availableFlagEnum = AvailableFlagEnum.fromValue(flagRiskDO.getAvailableFlag());
        if (AvailableFlagEnum.UNAVAILABLE == availableFlagEnum) {
            throw new ServerException("产品已禁用");
        }
        PublishFlagEnum publishFlagEnum = PublishFlagEnum.fromValue(flagRiskDO.getPublishFlag());
        if (PublishFlagEnum.PUBLISH != publishFlagEnum) {
            throw new ServerException("产品未发布，不可禁用");
        }
        AuditFlagEnum auditFlagEnum = AuditFlagEnum.fromValue(flagRiskDO.getAuditFlag());
        // todo 注销可能会跟审核流程挂钩。虽然想的是注销是注销掉已发布的产品，不管当前产品的配置状态。

        // 注销前先删除一遍缓存
        this.deleteRiskInCache(riskCode);

        Long riskReleaseId = transactionTemplate.execute(status -> {
            RiskDO updateRiskDO = new RiskDO().setId(riskId)
                    .setAvailableFlag(AvailableFlagEnum.UNAVAILABLE.getValue())
                    .setPublishFlag(PublishFlagEnum.NOT_PUBLISH.getValue())
                    .setSyncFlag(SyncFlagEnum.NOT_SYNC.getValue());
            int updateResult = riskMapper.updateById(updateRiskDO);
            if (updateResult <= 0) {
                throw new ServerException("启用产品失败.产品代码为 " + riskCode);
            }

            LambdaQueryWrapper<RiskReleaseDO> releaseQueryWrapper = Wrappers.lambdaQuery(RiskReleaseDO.class);
            releaseQueryWrapper.select(RiskReleaseDO::getId);
            releaseQueryWrapper.eq(RiskReleaseDO::getRiskId, riskId);
            releaseQueryWrapper.last("limit 1");
            RiskReleaseDO riskReleaseDO = riskReleaseMapper.selectOne(releaseQueryWrapper);

//            LambdaUpdateWrapper<RiskReleaseDO> releaseDeleteWrapper = Wrappers.lambdaUpdate();
//            releaseDeleteWrapper.eq(RiskReleaseDO::getRiskId, availableVO.getId());
//            releaseDeleteWrapper.last("limit 1");
//            int deleteResult = riskReleaseMapper.delete(releaseDeleteWrapper);

            int deleteResult = riskReleaseMapper.deleteById(riskReleaseDO.getId());
            if (deleteResult <= 0) {
                throw new ServerException("注销产品失败");
            }
            return riskReleaseDO.getId();
        });

        // 注销完成后再延迟删除一遍缓存
        ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(() -> {
            this.deleteRiskInCache(riskCode);
        }, Instant.now().plusSeconds(10));

//        this.deleteRiskInCache(riskCode);

        if (riskReleaseId != null) {
            riskSyncService.syncRisk2DataDict(riskReleaseId);
        }
    }

    /**
     * <p>根据产品代码删除缓存中涉及到的该产品的缓存
     *
     * @param riskCode  产品代码
     */
    public long deleteRiskInCache(String riskCode) {
        Assert.hasText(riskCode, "产品代码不能为空");
        String keyPattern = RedisKeyGenerateUtils.DEFAULT.generateMd5("releaseRisk", riskCode) + "*";
        KeysOptions defaults = KeysOptions.defaults();
        RKeys keys = redissonClient.getKeys(defaults);
        long deleteCount = keys.deleteByPattern(keyPattern);
        log.info("delete risk in cache, riskCode is {}, key pattern is {}, delete count is {}", riskCode, keyPattern, deleteCount);
        return deleteCount;
    }
}
